<?php

/*
 * jsrpc streaming data pseduocode
 *
 * var timeout = true;
 * set_time_limit( vry_hi_int )
 * register_shutdown_function( shutdown_hook )
 *
 * while( true ) {
 *   echo
 *   ob_end_flush()
 *   flush()
 * }
 * timeout = false;
 * exit
 *
 * function shutdown_hook() {
 *   if( timeout ) {
 *     // timed ou
 *   } else {
 *     // terminated normally
 *   }
 * }
 *
 */

/*
 * Response structure:
 *
 * {
 *   status: int // see status codes
 *   result: object
 *   error: object
 *   code: string // javascript code to be executed
 * }
 *
 * status codes:
 * 0xA0 - 0xAF Success
 * 0xB0 - 0xBF Error
 * 0xC0 - 0xCF ------
 */


class JSRPC {
	const Return_Object = 1;
	const Return_XML = 2;
	const Return_Text = 3;
	const Return_HTML = 4;

	const NoReturn = 0x01;
	const Async = 0x02;
	const Cachable = 0x04;
	const BrowserContext = 0x08;
	const Streaming = 0x10;

	private static $_debug = false;
	private static $_options = array();
	private static $_endpoint = '/';

	private static $_expose = array(
		'var' => array(),
		'func' => array(),
		'class' => array(),
		'reflect' => array(),
		'onload' => array(),
	);

	private static $_reserved = array(
		/*'break', 'continue', 'do', 'for', 'import', 'new', 'this', 'void', 'case', 'default', 'else', 'function',
		'in', 'return', 'typeof', 'while', 'comment', 'delete', 'export', 'if', 'label', 'switch', 'var', 'with',
		'abstract', 'implements', 'protected', 'boolean', 'instanceof', 'public', 'byte', 'int', 'short', 'char',
		'interface', 'static', 'double', 'long', 'synchronized', 'false', 'native', 'throws', 'final', 'null',
		'transient', 'float', 'package', 'true', 'goto', 'private', 'catch', 'enum', 'throw', 'class', 'extends',
		'try', 'const', 'finally', 'debugger', 'super', 'alert', 'eval', 'link', 'outerheight', 'scrollto', 'anchor',
		'fileupload', 'location', 'outerwidth', 'select', 'area', 'find', 'location', 'packages', 'self', 'arguments',
		'focus', 'locationbar', 'pagexoffset', 'setinterval', 'array', 'form', 'math', 'pageyoffset', 'settimeout',
		'assign', 'frame', 'menubar', 'parent', 'status', 'blur', 'frames', 'mimetype', 'parsefloat', 'statusbar',
		'boolean', 'function', 'moveby', 'parseint', 'stop', 'button', 'getclass', 'moveto', 'password', 'string',
		'callee', 'hidden', 'name', 'personalbar', 'submit', 'caller', 'history', 'nan', 'plugin', 'sun', 'captureevents',
		'history', 'navigate', 'print', 'taint', 'checkbox', 'home', 'navigator', 'prompt', 'text', 'clearinterval',
		'image', 'navigator', 'prototype', 'textarea', 'cleartimeout', 'infinity', 'netscape', 'radio', 'toolbar',
		'close', 'innerheight', 'number', 'ref', 'top', 'closed', 'innerwidth', 'object', 'regexp', 'tostring',
		'confirm', 'isfinite', 'onblur', 'releaseevents', 'unescape', 'constructor', 'isnan', 'onerror', 'reset',
		'untaint', 'date', 'java', 'onfocus', 'resizeby', 'unwatch', 'defaultstatus', 'javaarray', 'onload', 'resizeto',
		'valueof', 'document', 'javaclass', 'onunload', 'routeevent', 'watch', 'document', 'javaobject', 'open',
		'scroll', 'element', 'javapackage', 'opener', 'scrollbars', 'window', 'escape', 'length', 'option', 'scrollby',*/

		// reserved keywords
		'abstract', 'boolean', 'break', 'byte', 'case', 'catch', 'char', 'class', 'const', 'continue', 'debugger', 'default', 'delete', 'do', 'double', 'else', 'enum', 'export', 'extends', 'false', 'final', 'finally', 'float', 'for', 'function', 'goto', 'if', 'implements', 'import', 'in', 'instanceof', 'int', 'interface', 'long', 'native', 'new', 'null', 'package', 'private', 'protected', 'public', 'return', 'short', 'static', 'super', 'switch', 'synchronized', 'this', 'throw', 'throws', 'transient', 'true', 'try', 'typeof', 'var', 'volatile', 'void', 'while', 'with',

		// builtin objects
		'array', 'boolean', 'date', 'math', 'number', 'object', 'string', 'regexp',

		// "reserved" in browsers
		'window', 'screen',

		// window object functions and properties ------ SHOULDN'T BE ALLOWED AT THE TOP LEVEL, BUT SHOULD BE ALLOWED IN Object.[blank] = 'value';
		'alert', 'blur', 'clearinterval', 'cleartimeout', 'close', 'confirm', 'createpopup', 'focus', 'moveby', 'moveto', 'open', 'print', 'prompt', 'resizeby', 'resizeto', 'scrollby', 'scrollto', 'setinterval', 'settimeout',
		'closed', 'defaultstatus', 'document', 'frames', 'history', 'length', 'location', 'name', 'opener', 'outerHeight', 'outerwidth', 'pagexoffset', 'pageyoffset', 'parent', 'personalbar', 'scrollbars', 'self', 'status', 'statusbar', 'toolbar', 'top',

		// top-level functions and properties
		'decodeuri', 'decodeuricomponent', 'encodeuri', 'encodeuricomponent', 'escape', 'eval', 'isfinite', 'isnan', 'number', 'parsefloat', 'parseint', 'string', 'unescape',
		'infinity', 'nan', 'undefined',

		// non language reserved, stuff exposed by core_lib.js and various js libraries
		'$', '$$', '__php', 'ajax', 'base64', 'json', 'jsrpc', 'jquery', 'oni', 'y', 'yahoo',
	);

	private static $_entities = array(
		"\xa0" => "&#xa0;", "\xa1" => "&#xa1;", "\xa2" => "&#xa2;", "\xa3" => "&#xa3;", "\xa4" => "&#xa4;",
		"\xa5" => "&#xa5;", "\xa6" => "&#xa6;", "\xa7" => "&#xa7;", "\xa8" => "&#xa8;", "\xa9" => "&#xa9;",
		"\xaa" => "&#xaa;", "\xab" => "&#xab;", "\xac" => "&#xac;", "\xad" => "&#xad;", "\xae" => "&#xae;",
		"\xaf" => "&#xaf;", "\xb0" => "&#xb0;", "\xb1" => "&#xb1;", "\xb2" => "&#xb2;", "\xb3" => "&#xb3;",
		"\xb4" => "&#xb4;", "\xb5" => "&#xb5;", "\xb6" => "&#xb6;", "\xb7" => "&#xb7;", "\xb8" => "&#xb8;",
		"\xb9" => "&#xb9;", "\xba" => "&#xba;", "\xbb" => "&#xbb;", "\xbc" => "&#xbc;", "\xbd" => "&#xbd;",
		"\xbe" => "&#xbe;", "\xbf" => "&#xbf;", "\xc0" => "&#xc0;", "\xc1" => "&#xc1;", "\xc2" => "&#xc2;",
		"\xc3" => "&#xc3;", "\xc4" => "&#xc4;", "\xc5" => "&#xc5;", "\xc6" => "&#xc6;", "\xc7" => "&#xc7;",
		"\xc8" => "&#xc8;", "\xc9" => "&#xc9;", "\xca" => "&#xca;", "\xcb" => "&#xcb;", "\xcc" => "&#xcc;",
		"\xcd" => "&#xcd;", "\xce" => "&#xce;", "\xcf" => "&#xcf;", "\xd0" => "&#xd0;", "\xd1" => "&#xd1;",
		"\xd2" => "&#xd2;", "\xd3" => "&#xd3;", "\xd4" => "&#xd4;", "\xd5" => "&#xd5;", "\xd6" => "&#xd6;",
		"\xd7" => "&#xd7;", "\xd8" => "&#xd8;", "\xd9" => "&#xd9;", "\xda" => "&#xda;", "\xdb" => "&#xdb;",
		"\xdc" => "&#xdc;", "\xdd" => "&#xdd;", "\xde" => "&#xde;", "\xdf" => "&#xdf;", "\xe0" => "&#xe0;",
		"\xe1" => "&#xe1;", "\xe2" => "&#xe2;", "\xe3" => "&#xe3;", "\xe4" => "&#xe4;", "\xe5" => "&#xe5;",
		"\xe6" => "&#xe6;", "\xe7" => "&#xe7;", "\xe8" => "&#xe8;", "\xe9" => "&#xe9;", "\xea" => "&#xea;",
		"\xeb" => "&#xeb;", "\xec" => "&#xec;", "\xed" => "&#xed;", "\xee" => "&#xee;", "\xef" => "&#xef;",
		"\xf0" => "&#xf0;", "\xf1" => "&#xf1;", "\xf2" => "&#xf2;", "\xf3" => "&#xf3;", "\xf4" => "&#xf4;",
		"\xf5" => "&#xf5;", "\xf6" => "&#xf6;", "\xf7" => "&#xf7;", "\xf8" => "&#xf8;", "\xf9" => "&#xf9;",
		"\xfa" => "&#xfa;", "\xfb" => "&#xfb;", "\xfc" => "&#xfc;", "\xfd" => "&#xfd;", "\xfe" => "&#xfe;",
		"\xff" => "&#xff;", "\x22" => "&#x22;", "\x3c" => "&#x3c;", "\x3e" => "&#x3e;", "\x26" => "&#x26;"
	);

	private static $_reflect_func_map = array(
		'count',
		'rand',
		'srand',
		'getrandmax',
		'strpos',
		'strrpos',
		'stripos',
		'strripos',
		'strrev',
		'strrev',
		'strrev',
		'is_finite',
		'is_infinite',
		'is_nan',
		'base64_encode',
		'base64_decode',
		'implode',
		'explode',
		'sort',
		'array_search',
		'array_reverse',
		'array_push',
		'array_pop',
		'array_shift',
		'array_unshift',
		'array_slice',
		'array_splice',
		'array_keys',
		'array_values',
		'json_encode',
		'json_decode'
	);

	private function __construct() {}

	public static function HasReflectedFunctions() {
		return count(self::$_expose['reflect']) > 0;
	}

	public static function HasRPCFunctions() {
		return count(self::$_expose['func']) || count(self::$_expose['class']);
	}

	public static function Handle() {
		if (isset($_SERVER['HTTP_X_JSRPC_SIGNATURE'])) {
			set_error_handler(array("JSRPC", "ErrorHandler"));

			try {
				$sig = $_SERVER['HTTP_X_JSRPC_SIGNATURE'];
				$func = $_SESSION['js_rpc'][$sig];

				if ($func['sig'] != $sig) {
					throw new Exception('JSRPC Error', 1);
				}

				self::$_options = $func['options'];

				$args = $_REQUEST['a'];

				$encoding = strtolower($_SERVER['HTTP_X_JSRPC_ENCODING']);
				if ($encoding == 'b64' || $encoding == 'base64') {
					$args = base64_urldecode($args);
				}

				$args = self::JsonDecode($args);

				ob_start();

				if ($func['file']) {
					require_once $func['file'];
				}

				if (class_exists('jQuery', false)) {
					jQuery::StartBuffering();
				}

				$ret = call_user_func_array($func['name'], $args);

				ob_end_clean();

				$ret = array(
					'status' => 0xA0,
					'result' => $ret,
				);

				if (class_exists('jQuery', false)) {
					$code = jQuery::GetBufferedCode();
					if ($code !== false) {
						$ret['code'] = $code;
					}
				}

				self::ReturnValue($ret);
			} catch (Exception $ex) {
				self::ReturnValue(array(
					'status' => 0xB0,
					'error' => (array)$ex
				));
			}

			exit;
		}
	}

	public static function SetFormHandler($form_id, $func, array $options = array()) {
		$options_json = json_encode($options);
		self::$_expose['onload']['form_handler#'.$func] = "JSRPC.SetFormHandler('$form_id', $func, $options_json);";
	}

	public static function ExposeFunction($func, array $options = array()) {
		if (!is_callable($func)) {
			return false;
		}

		if (!isset($options['debug']) && self::$_debug) {
			$options['debug'] = self::$_debug;
		}

		$options_json = json_encode($options);

		if (is_string($func) && strpos($func, '::')) {
			$func = explode('::', $func);
		}

		if (is_array($func)) {
			if (count($func) != 2 || !is_string($func[0]) || !method_exists($func[0], $func[1])) {
				return false;
			}

			$classname = $func[0];
			$methodname = $func[1];

			if (!class_exists($classname, true) || !method_exists($classname, $methodname)) {
				return false;
			}

			$classname_clean = self::ValidateObjectName($classname);
			$methodname_clean = self::ValidateObjectName($methodname);

			if (!$classname_clean || !$methodname_clean) {
				return false;
			}

			$class = new ReflectionClass($classname);
			$method = $class->getMethod($methodname);

			$func = $class->getName() . '::' . $method->getName();

			if (!isset($_SESSION['js_rpc'][$func])) {
				$rnd = dechex(mt_rand() ^ hexdec(uniqid()));
				$sig = base64_urlencode(md5($rnd . '#' . $class->getFileName() . '@' . $func, true));
				$_SESSION['js_rpc'][$func] = $sig;
				$_SESSION['js_rpc'][$sig] = array(
					'class'		=> $class->getName(),
					'method'	=> $method->getName(),
					'name'		=> array($class->getName(), $method->getName()),
					'file'		=> $class->getFileName(),
					'sig'		=> $sig,
					'options'	=> $options
				);
			} else {
				$sig = $_SESSION['js_rpc'][$func];
			}

			self::$_expose['class'][$classname_clean]['method'][$methodname_clean] = "\t$methodname_clean : JSRPC.Method('$sig', $options_json)";

			$func = $classname_clean . '.' . $methodname_clean;
		} else {
			if (!function_exists($func)) {
				return false;
			}

			$reflect = new ReflectionFunction($func);

			if (!($func = self::ValidateObjectName($reflect->getName()))) {
				return false;
			}

			if (!isset($_SESSION['js_rpc'][$func])) {
				$rnd = dechex(mt_rand() ^ hexdec(uniqid()));
				$sig = base64_urlencode(md5($rnd . '#' . $reflect->getFileName() . '@' . $reflect->getName(), true));
				$_SESSION['js_rpc'][$func] = $sig;
				$_SESSION['js_rpc'][$sig] = array(
					'name'		=> $reflect->getName(),
					'internal'	=> $reflect->isInternal(),
					'file'		=> $reflect->getFileName(),
					'sig'		=> $sig,
					'options'	=> $options
				);
			} else {
				$sig = $_SESSION['js_rpc'][$func];
			}

			//self::$_expose['func'][$func] = "var $func = JSRPC.Method('$sig', $options_json);";
			self::$_expose['func'][$func] = "$func = JSRPC.Method('$sig', $options_json)";
		}

		return $func;
	}

	public static function ExposeVariable($name, $value = null, $encode_entities = false) {
		if (strpos($name, '::')) {
			$name = explode('::', $name);
		}

		if (is_array($name)) {
			/*$class = $name[0];
			$property = $name[1];*/

			list($classname, $propertyname) = $name;

			if (func_num_args() == 1) {
				$value = null;
				if (is_object($classname)) {
					if (property_exists($classname, $propertyname)) {
						$value = $classname->$propertyname;
					}
				} else if (is_string($classname)) {
					if (class_exists($classname) && property_exists($classname, $propertyname)) {
						//$value = $classname::$$propertyname;
						$refl = new ReflectionClass($classname);
						$value = $refl->getStaticPropertyValue($propertyname);
					}
				}
			} else {
				if (is_object($classname)) {
					$classname = get_class($classname);
				}
			}

			$classname_clean = self::ValidateObjectName($classname);
			$propertyname_clean = self::ValidateObjectName($propertyname);

			if ($classname_clean && $propertyname_clean) {
				$var = self::Types($value, $encode_entities);
				self::$_expose['class'][$classname_clean]['var'][$propertyname_clean] = "\t$propertyname_clean : $var";
			}
		} else {
			if ($name_clean = self::ValidateObjectName($name)) {
				$var = self::Types($value, $encode_entities);
				//self::$_expose['var'][$name_clean] = "var $name_clean = $var;";
				self::$_expose['var'][$name_clean] = "$name_clean = $var";
			}
		}

		return $name;
	}

	public static function SetReturnType() {

	}

	public static function SetStreaming() {

	}

	/*
	 * GenerateJSDL()
	 */
	public static function GenerateJSDL() {
		//$method

		$out = array(
			'type' => 'jsdl',
			'endpoint' => self::$_endpoint,
			'session_id' => session_id(),
			'methods' => $methods,
			'classes' => $classes
		);

		return json_encode($out);
	}

	/*
	 * Finalize()
	 */
	public static function Finalize() {
		$var = array();
		$exposed = array();

		if (count(self::$_expose['func']) || count(self::$_expose['class'])) {
			$exposed[] = 'JSRPC.Endpoint = ' . json_encode(self::$_endpoint) . ';';
		}

		if (count(self::$_expose['var'])) {
			//$exposed[] = implode("\n", self::$_expose['var']);
			$var += self::$_expose['var'];
		}

		if (count(self::$_expose['func'])) {
			//$exposed[] = implode("\n", self::$_expose['func']);
			$var += self::$_expose['func'];
		}

		if (count(self::$_expose['class'])) {
			foreach (self::$_expose['class'] as $class => $members) {
				$all_members = array();
				if (is_array($members['var'])) {
					$all_members += $members['var'];
				}
				if (is_array($members['method'])) {
					$all_members += $members['method'];
				}
				$members = implode(",\n", $all_members);
				//$exposed[] = "var $class = JSRPC.ExtendStatic($class, {\n$members\n});";
				$var[] = "$class = JSRPC.ExtendStatic($class, {\n$members\n})";
			}
		}

		if (count($var)) {
			$exposed[] = "var\n" . implode(",\n", $var) . ";";
		}

		if (count(self::$_expose['reflect'])) {
			$funcs = array_keys(self::$_expose['reflect']);
			foreach ($funcs as $func) {
				$exposed[] = self::GenerateReflectFunction($func);
			}
		}

		if (count(self::$_expose['onload'])) {
			$exposed[] = "$(function() {\n" . implode("\n", self::$_expose['onload']) . "\n});";
		}

		return count($exposed) ? implode("\n", $exposed) : false;
	}

	/*
	 * ValidateObjectName()
	 * Ensures only valid variable names can be used
	 */
	private static function ValidateObjectName($name) {
		$name = preg_replace('/([^a-z0-9_]+|^[0-9]+|[_]+$)/i', '', $name);

		if (strlen($name) == 0 || in_array(strtolower($name), self::$_reserved)) {
			$name = false;
		}

		return $name;
	}

	public static function SetDebug($jsfunc = false) {
		self::$_debug = $jsfunc;
	}

	public static function SetEndpoint($uri) {
		self::$_endpoint = $uri;
	}

	private static function ReturnValue($value) {
		while (ob_get_level() != 0) {
			ob_end_clean();
		}

		$value = self::Types($value, isset(self::$_options['encode']) ? self::$_options['encode'] : false);

		if (strlen($value) > 1024 && isset($_SERVER['HTTP_ACCEPT_ENCODING']) && strpos($_SERVER['HTTP_ACCEPT_ENCODING'], 'gzip') !== false) {
			header('Content-Encoding: gzip');
			$value = gzencode($value, 9, FORCE_GZIP);
		}

		header('Content-Type: application/json; charset=UTF-8');
		//header('Content-Type: application/json');
		header('Content-MD5: ' . md5($value));
		header('Content-Length: ' . strlen($value));

		if (self::$_options['cache']) {
			header('Cache-Control: public');
			header('Pragma: ');
			header('Last-Modified: Thu, 1 Jan 1970 00:00:00 GMT');
			header('Expires: Mon, 18 Jan 2038 00:00:00 GMT');
		}

		echo $value;

		flush();

		exit;
	}

	private static function ReturnException($ex) {
		if (!($ex instanceof Exception)) {
			self::ReturnValue($ex);
		}

		while (ob_get_level() != 0) {
			ob_end_clean();
		}

		header("Content-Type: application/json");

		if ($_COOKIE['jsrpc_verbose_errors'] != 'true') {
			// need another way to do this now that we're using JSON2.js instead of eval
			//printf('new JSRPCException(%s, %s)', self::EscapeString($ex->getMessage()), $ex->getCode());
		} else {
			// need another way to do this now that we're using JSON2.js instead of eval
			//printf('new JSRPCException(%s, %s, %s)', self::EscapeString($ex->getMessage()), $ex->getCode(), self::EscapeString($ex->getTraceAsString()));
		}

		flush();

		exit;
	}

/*	public static function HandleError($errno, $errstr, $errfile = '', $errline = -1, array $errcontext = array()) {
		set_error_handler(array('JSRPC', 'HandleError'), E_ALL);
		self::ReturnException(new Exception($errfile.' '.$errstr, $errline));
		//self::ReturnException(new Exception($errstr, $errno));
} */

	private static function Types($value, $encode_entities = false) {
		try {
			if ($value === null || (class_exists('DBNull', false) && $value instanceof DBNull)) {
				return 'null';
			} else if ($value === true) {
				return 'true';
			} else if ($value === false) {
				return 'false';
			} else if (is_array($value)) {
				return json_encode(self::Prepare($value, false, $encode_entities));
			} else if (is_string($value)) {
				return self::EscapeString($value, $encode_entities);
			} else if (is_numeric($value)) {
				return $value;
			} else if (is_object($value)) {
				return json_encode(self::Prepare($value, $encode_entities));
			} else {
				return 'null';
			}
		} catch (Exception $ex) {
			return 'null';
		}
	}

	private static function EscapeString($value, $encode_entities = false) {
		$value = str_replace(
			array("\r", "\n", "\t"), //search
			array('\r', '\n', '\t'), //replace
			addcslashes($value, '"') //subject
		);

		return sprintf('"%s"', $encode_entities ? strtr($value, self::$_entities) : $value);
	}

	private static function Prepare(&$value, $key = false, $encode_entities = false) {
		if (is_array($value)) {
			array_walk($value, array('JSRPC', 'Prepare'), $encode_entities);
		} else if (is_object($value)) {
			if (class_exists('DBNull', false) && $value instanceof DBNull) {
				$value = null;
			} else if ($value instanceof Traversable || $value instanceof stdClass) {
				$obj2array = array();
				foreach ($value as $key => $val) {
					$obj2array[$key] = self::Prepare($val, false, $encode_entities);
				}
				$value = $obj2array;
			} else {
				$value = $encode_entities ? strtr(strval($value), self::$_entities) : strval($value);
			}
		} else if (is_string($value) && $encode_entities) {
			$value = strtr($value, self::$_entities);
		}

		return $value;
	}

	private static function JsonDecode($args) {
		//return self::ObjectToArray(json_decode($args));
		return self::Prepare(json_decode($args, true));
	}

	private static function ObjectToArray($obj) {
		if ($obj instanceof stdClass || $obj instanceof Traversable || is_array($obj)) {
			$arr = array();
			foreach ($obj as $key => $val) {
				$arr[$key] = self::ObjectToArray($val);
			}
			$obj = $arr;
		}

		return $obj;
	}

	public static function ErrorHandler($errno, $errstr, $errfile, $errline) {
		//Log::				// log error
		switch($errno) {
			case E_USER_ERROR:
				self::ReturnException(new Exception($errfile . ': ' . $errstr, $errno));
				break;
			case E_USER_WARNING:
			case E_USER_NOTICE:
			default:
		}

		return true;
	}

	public static function ReflectFunction($func) {
		self::$_expose['reflect'][$func] = true;
	}

	public static function GenerateReflectFunction($func) {
		if (!is_callable($func) || !function_exists($func)) {
			return false;
		}

		$refl = new ReflectionFunction($func);

		$file_code = file_get_contents($refl->getFileName());

		$file_lines = explode("\n", $file_code);

		$start_line = $refl->getStartLine() - 1;
		$length_lines = $refl->getEndLine() - $start_line;

		$func_lines = array_slice($file_lines, $start_line, $length_lines);
		$func_code = '<'."?php\n" . implode("\n", $func_lines);

		$tokens = token_get_all($func_code);

		$in_func = false;
		$js_code = "";

		$vars = array();
		$vars_param = array();
		$vars_global = array();
		$vars_static = array();

		$counterid = 0;

		for ($i = 0; $i < count($tokens); ++$i) {
			while ($in_func === false) {
				if (is_array($tokens[$i]) && $tokens[$i][0] === T_FUNCTION
					&& is_array($tokens[$i + 2]) && $tokens[$i + 2][0] === T_STRING
					&& $tokens[$i + 2][1] === $refl->getName())
				{
					$params = array();
					$param_defaults = array();
					foreach ($refl->getParameters() as $param) {
						$n = $param->getName();
						$params[] = $n;
						$vars_param[$n] = true;
						if ($param->isDefaultValueAvailable()) {
							$json = json_encode($param->getDefaultValue());
							$param_defaults[] = "(typeof $n != 'undefined') || ($n = $json);";
						}
					}
					$params = implode($params, ", ");
					$param_defaults = implode($param_defaults, "\n\t");
					$js_code = "function {$refl->getName()}({$params}) {\n\t{$param_defaults}\n\t//VARDECS//";
					$in_func = 1;
					while ($tokens[++$i] != '{');
				}
				++$i;
			}

			if ($in_func !== false) {
				$token = $tokens[$i];
				if (is_array($token)) {
					switch ($token[0]) {

						case T_STRING:
							if ($token[1] == 'j') {
								$js_code .= '$';
							} else if ($token[1] == 'func_num_args') {
								$js_code .= 'arguments.length';
								while ($tokens[$i] !== ')') ++$i;
							} else if ($token[1] == 'func_get_args') {
								$js_code .= 'arguments';
								while ($tokens[$i] !== ')') ++$i;
							} else if ($token[1] == 'func_get_arg') {
								$js_code .= 'arguments[';
								while (!is_array($tokens[$i]) || !($tokens[$i][0] == T_LNUMBER || $tokens[$i][0] == T_VARIABLE)) ++$i;
								$val = $tokens[$i][0] == T_VARIABLE ? substr($tokens[$i][1], 1) : $tokens[$i][1];
								$js_code .= $val . ']';
								while ($tokens[++$i] !== ')');
							} else {
								$func = $token[1];
								if (in_array(strtolower($func), self::$_reflect_func_map)) {
									$func = '__php.' . strtolower($func);
								}
								$js_code .= $func;
							}
							break;

						case T_CONSTANT_ENCAPSED_STRING:
							$js_code .= $token[1];
							break;

						case T_OBJECT_OPERATOR:
						case T_DOUBLE_COLON:
							$js_code .= '.';
							break;

						case T_CONCAT_EQUAL:
							$js_code .= '+=';
							break;

						case T_VARIABLE:
							$var_name = substr($token[1], 1);
							if (isset($vars_static[$var_name])) {
								$js_code .= 'JSRPC.Static["'.$refl->getName().'"]["'.$var_name.'"]';
							} else {
								if (!isset($vars[$var_name])) {
									$vars[$var_name] = true;
								}
								$js_code .= $var_name;
							}
							break;

						case T_ARRAY:
							$js_code .= json_encode(self::ParseArrayTokens($tokens, &$i));
							break;

						case T_FUNCTION:
							$js_code .= 'function';
							break;

						case T_STATIC:
							while ($tokens[$i++] !== ';') {
								if (is_array($tokens[$i]) && $tokens[$i][0] == T_VARIABLE) {
									$var_name = substr($tokens[$i][1], 1);
									while ((is_array($tokens[++$i]) && ($tokens[$i][0] == T_WHITESPACE)) || $tokens[$i] == '=');
									$val = $tokens[$i] == ';' ? 'null' : ($tokens[$i][0] == T_ARRAY ? json_encode(self::ParseArrayTokens($tokens, $i)) : $tokens[$i][1]);
									$vars_static[$var_name] = sprintf('JSRPC.Static("%s", "%s", %s);', $refl->getName(), $var_name, $val);
								}
							}
							break;

						case T_GLOBAL:
							while ($tokens[$i] !== ';') {
								++$i;
								if (is_array($tokens[$i]) && $tokens[$i][0] == T_VARIABLE) {
									$var_name = substr($tokens[$i][1], 1);
									$vars_global[$var_name] = true;
								}
							}
							break;

						case T_CATCH:
							while (!is_array($tokens[++$i]) || $tokens[$i][0] != T_VARIABLE);
							$js_code .= 'catch (' . substr($tokens[$i][1], 1);
							break;

						case T_ELSEIF:
							$js_code .= 'else if';
							break;

						case T_FOREACH:
							$current = 'array';
							$clauses = null;
							while (is_array($tokens[++$i]) || $tokens[$i] != ')') {
								if ($tokens[$i] == '(') {
									$clauses = array();
								} else if (!is_null($clauses) && is_array($tokens[$i]) && $tokens[$i][0] != T_WHITESPACE) {
									switch ($tokens[$i][0]) {
										case T_VARIABLE:
											$clauses[$current] = substr($tokens[$i][1], 1);
											// TODO: needs to be able to handle expressions placed in the foreach:
										// foreach ($object->getValues() as $key => $value)
											break;
										case T_AS:
											$current = 'value';
											break;
										case T_DOUBLE_ARROW:
											$current = 'value';
											$clauses['key'] = $clauses['value'];
											break;
										default:
											//unprepared for this, is it a possibility???? yes, yes it is
											break;
									}
								}
							}
							// need to handle foreaches that arent wrapped in semicolons! currently looks for an ending semicolon
							while (is_array($tokens[++$i]) || $tokens[$i] != '{');
							++$in_func;
							if (isset($clauses['key'])) {
								$array = $clauses['array'];
								$key = $clauses['key'];
								$value = $clauses['value'];
								$obj = '__tmp' . $counterid++; // array
								$vars[$obj] = true;
								$vars[$key] = true;
								$vars[$value] = true;
								$js_code .= "$obj = __php._toIterable($array); for ($key in $obj) { if (!$obj.hasOwnProperty($key)) continue; $value = $obj"."[$key]; ";
							} else {
								$array = $clauses['array'];
								$value = $clauses['value'];
								$arr = '__tmp' . $counterid++; // array
								$ctr = '__tmp' . $counterid++; // counter
								$vars[$arr] = true;
								$vars[$ctr] = true;
								$vars[$value] = true;
								$js_code .= "$arr = __php._toArray($array); for ($ctr = 0; $ctr < $arr.length; ++$ctr) { $value = $arr"."[$ctr]; ";
							}
							break;

						case T_WHITESPACE:
							$js_code .= $token[1];
							break;

						default:
							$js_code .= $token[1];
							break;

						case T_OPEN_TAG:
						case T_CLOSE_TAG:
							throw new SyntaxException();
							break;

					}
				} else if ($token == '.') {
					$js_code .= '+';
				} else if ($token == '{' || $token == '}') {
					if ($token == '{') {
						++$in_func;
					} else {
						--$in_func;
					}

					$js_code .= $token;

					if ($in_func === 0) {
						break;
					}
				//} else if ($token == ';') {
					//$js_code .= ";\n" . str_repeat("\t", $in_func);
				} else {
					$js_code .= $token;
					//echo "Write: $token\n";
				}
			}
		}

		$vars = array_diff_assoc($vars, $vars_global, $vars_static, $vars_param);

		$var_decs = '';
		if (count($vars) > 0) {
			$var_decs = "var " . implode(array_keys($vars), ', ') . ";\n\t";
		}

		$var_decs .= implode($vars_static, "\n\t");
		$js_code = str_replace("//VARDECS//", $var_decs, $js_code);

		return $js_code;
	}

	private static function ParseArrayTokens(array $tokens, &$i = 0) {
		while (!(is_array($tokens[$i]) && $tokens[$i][0] == T_ARRAY)) ++$i;
		while (is_array($tokens[$i]) || $tokens[$i] != '(') ++$i;

		$array = array();
		$cur_val = null;
		$cur_key = null;

		do {
			++$i;
			if (is_array($tokens[$i])) {
				if ($tokens[$i][0] == T_ARRAY) {
					$cur_val = self::ParseArrayTokens($tokens, &$i);
					++$i;
				} else if ($tokens[$i][0] == T_DOUBLE_ARROW) {
					$cur_key = $cur_val;
					$cur_val = null;
				} else if ($tokens[$i][0] != T_WHITESPACE) {
					$cur_val = $tokens[$i][1];
					if ($tokens[$i][0] == T_CONSTANT_ENCAPSED_STRING) {
						$quote = $tokens[$i][1][0];
						$cur_val = substr($tokens[$i][1], 1, -1);
						$cur_val = $quote == "'" ? stripslashes($cur_val) : stripcslashes($cur_val);
					} else if ($tokens[$i][0] == T_LNUMBER || $tokens[$i][0] == T_DNUMBER) {
						$cur_val = $tokens[$i][1];
					} else {
						echo token_name($tokens[$i][0]);
					}
				}
			} else {
				if ($tokens[$i] == ')' || $tokens[$i] == ',') {
					if (!is_null($cur_val)) {
						if (!is_null($cur_key)) {
							$array[$cur_key] = $cur_val;
						} else {
							$array[] = $cur_val;
						}
					}
				}
			}
		} while (is_array($tokens[$i]) || $tokens[$i] != ')');

		return $array;
	}

}

abstract class JSRPCExceptionBase extends Exception {
	
}

class JSRPCException extends JSRPCExceptionBase {

}

function base64_urlencode($s) {
	$s = base64_encode($s);
	return str_replace(
		array('+', '/', '='),
		array('-', '_', ''),
		$s
	);
}

function base64_urldecode($b) {
	$b = str_replace(
		array('-', '_'),
		array('+', '/'),
		$b
	);
	return rtrim(base64_decode($b), "\0");
}
